package persistencia;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;

import javax.crypto.Cipher;
import javax.crypto.SealedObject;
import javax.crypto.SecretKey;

import criptografia.Chave;

/**
 * Classe que utiliza o principio da serializacao de objetos para criar a persistencia em arquivos.
 * Essa classe utiliza-se de um mecanismo de criptografia para garantir a seguraca dos dados armazenados. 
 * @author Equipe Localisa, usa quem precisa
 *
 */
public class MecanismoDePersistenciaSeguro {
	
	private String nomeCompletoDoArquivo;
	private Cipher cipher ;
	
	private SecretKey chaveSecreta;
		
	/**
	 * Construtor que ja encapsula a funcionalidade de buscar a chave
	 * @param String - nomeCompletoDoArquivo
	 */
	public MecanismoDePersistenciaSeguro(String nomeCompletoDoArquivo) throws Exception{
		
		File arquivo = new File(nomeCompletoDoArquivo);
		cipher = Cipher.getInstance("AES");
		
		if(!arquivo.exists()){
			/*Ja tratei as execessoes aqui porque so eh possivel acontecer
			  uma e ela ja eh tratada por esse if acima.
			*/
			try{
				Chave chave = new Chave();
				chaveSecreta = chave.getKey();
				FileOutputStream arquivoSaida = new FileOutputStream("arquivos/X1.ser");
				ObjectOutputStream saida = new ObjectOutputStream(arquivoSaida);
				saida.writeObject(chave);
			}catch(Exception e){
				System.out.println("\n \t <Erro na gravacao da chave de criptografia!>");
			}
		}
		
		//Caso ja existe o arquivo com a chave
		else{
			try{
				/*
				 Ja tratei pelo menos motivo acima
				 */
				FileInputStream arquivoEntrada = new FileInputStream("arquivos/X1.ser");
				ObjectInputStream entrada = new ObjectInputStream(arquivoEntrada);
				Chave chave = (Chave) entrada.readObject();//Le o objeto chave e faz um downcast
				chaveSecreta = chave.getKey();
				
			}catch(Exception e){
				
				System.out.println("\n \t <Erro na leitura da chave de criptografia!>");
			}
		}
		
		this.nomeCompletoDoArquivo = nomeCompletoDoArquivo;
		 
	}
	
	/**
	 * Guarda todos os elementos presentes no arrayList passado como paramentro
	 * @param ArrayList - lista
	 * @return boolean - true caso a persistencia ocorra com sucesso e false caso ocorra algum problema
	 * @throws Exception - Excecoes de IO
	 */
	@SuppressWarnings("unchecked")
	public boolean persisteNoArquivo(ArrayList lista) throws Exception{
		
		this.cipher = Cipher.getInstance("AES");
		this.cipher.init(Cipher.ENCRYPT_MODE, this.chaveSecreta);
		
		if(lista == null){
			throw new Exception("\n \t <Lista de elementos para ser persistida nao pode ser nula!>");
		}
		
		try{	
			SealedObject objetoSelado = null;
			
			Iterator iterador = lista.iterator();
			 
			ObjectOutputStream saidaParaArquivo = new ObjectOutputStream(new FileOutputStream(this.nomeCompletoDoArquivo));
			
			while(iterador.hasNext()){
				
				objetoSelado = new SealedObject((Serializable) iterador.next(), cipher);
				saidaParaArquivo.writeObject(objetoSelado);
			}
			saidaParaArquivo.close();
			return true;
			
			
			}catch(Exception e){
			System.out.println(e.getCause());
			return false;
		}
		
		
	}
	/**
	 * Retorna um arrayList de todos os objetos recuperados do arquivo setado no construtor 
	 * @return Uma lista com a informacao recuperada do arquivo
	 * @throws IOException 
	 */
	@SuppressWarnings("unchecked")
	public ArrayList recuperaDoArquivo() throws Exception{
		
		this.cipher.init(Cipher.DECRYPT_MODE, this.chaveSecreta);
		
		ArrayList arrayListAuxiliar = new ArrayList();
		ObjectInputStream entradaParaArquivo = null;
		
		try{
			entradaParaArquivo = new ObjectInputStream(new FileInputStream(nomeCompletoDoArquivo));
			
			Object obj;
			SealedObject sos;
			
			while(( sos = (SealedObject) entradaParaArquivo.readObject()) != null){
				
				obj = sos.getObject(chaveSecreta);
				
				arrayListAuxiliar.add(obj);
			}
			
			return arrayListAuxiliar;
			
		}catch(java.io.EOFException e){
			return arrayListAuxiliar;
		}
		catch(Exception e){
			return arrayListAuxiliar;
		
		}
		finally{
			if(entradaParaArquivo != null){
				entradaParaArquivo.close();
			}	
		}
	}
	
	/**
	 * Persiste em um arquivo apenas um objeto
	 * @param obj - Objeto que deseja persistir
	 * @throws Exception Excecoes de escrita no arquivo e se o obj passado for null
	 */
	public void persisteObjetoNoArquivo(Object obj) throws Exception{
		
		this.cipher.init(Cipher.ENCRYPT_MODE, this.chaveSecreta);

		ObjectOutputStream saidaParaArquivo = null;
	
		try{
			File file = new File(this.nomeCompletoDoArquivo);
			if(file.exists()){
				file.delete();
			}
			saidaParaArquivo = new ObjectOutputStream(new FileOutputStream(file));
			SealedObject objSelado = new SealedObject((Serializable) obj, cipher);
			saidaParaArquivo.writeObject(objSelado);
		
			saidaParaArquivo.close();
		}catch(Exception e){
			System.out.println(e.getCause());
		}
		finally{
			if(saidaParaArquivo != null){
				saidaParaArquivo.close();
			}
		}
		
	}	
	
	/**
	 * Recupera um arquivo que foi persistido utilizando o metodo persiteObjetoNoArquivo
	 * @return Um objeto ja descriptografado
	 * @throws Exception Se houver problemas com a leitura do arquivo
	 */
	public Object recuperaObjetoNoArquivo() throws Exception{
		
		ObjectInputStream entradaParaArquivo = null;
		Object obj = null;
		this.cipher.init(Cipher.DECRYPT_MODE, this.chaveSecreta);
		
		try{
			File file =  new File(this.nomeCompletoDoArquivo);
			if(!file.exists()){
				file.createNewFile();
			}
			entradaParaArquivo = new ObjectInputStream(new FileInputStream(file));
			
			SealedObject os = (SealedObject) entradaParaArquivo.readObject();
			obj = os.getObject(chaveSecreta);
		
			return obj;
		
		}catch(java.io.EOFException e){
			return obj;
		}
		catch(Exception e){
			return obj;
		
		}	
		finally{
			if(entradaParaArquivo != null){
				entradaParaArquivo.close();
			}
		}

		
	}
	
}